/*
 $Id$

 Copyright (C) 2006-2007 by David Cotton

 This program is free software; you can redistribute it and/or modify it under
 the terms of the GNU General Public License as published by the Free Software
 Foundation; either version 2 of the License, or (at your option) any later
 version.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along with
 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
 Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package fr.free.jchecs.ai;

import static fr.free.jchecs.ai.AbstractEngine.MATE_VALUE;
import static fr.free.jchecs.core.Constants.FILE_COUNT;
import static fr.free.jchecs.core.Constants.RANK_COUNT;
import static fr.free.jchecs.core.PieceType.KING;
import static fr.free.jchecs.core.PieceType.PAWN;

import fr.free.jchecs.core.Piece;
import fr.free.jchecs.core.PieceType;
import fr.free.jchecs.core.Square;
import fr.free.jchecs.core.move.MoveGenerator;

/**
 * Evaluation function based on the hardware, the position of pieces on the
 * board and mobility.
 * <p>
 * Class safe screw threads.
 * </p>
 * 
 * @author David Cotton
 */
final class MobilityHeuristic implements Heuristic {
	/** Number of pieces from which we consider to be end game. */
	private static final int END_GAME = 8;

	/** Number of parts from which it is considered to be in the middle game. */
	private static final int MIDDLE_GAME = 16;

	/** Class identifier for serialization. */
	private static final long serialVersionUID = 8752973612245818678L;

	/**
	 * Bonus / Malus for a fool (white by default) based on its position.
	 */
	private static final int[] BISHOP_POSITIONS = { -5, -5, -5, -5, -5, -5, -5,
			-5, // a1 ... h1
			-5, 10, 5, 10, 10, 5, 10, -5, // a2 ... h2
			-5, 5, 3, 12, 12, 3, 5, -5, // a3 ... h3
			-5, 3, 12, 3, 3, 12, 3, -5, // a4 ... h4
			-5, 3, 12, 3, 3, 12, 3, -5, // a5 ... h5
			-5, 5, 3, 12, 12, 3, 5, -5, // a6 ... h6
			-5, 10, 5, 10, 10, 5, 10, -5, // a7 ... h7
			-5, -5, -5, -5, -5, -5, -5, -5, // a8 ... h8
	};
	static {
		assert BISHOP_POSITIONS.length == 64;
	}

	/**
	 * Bonus / Malus for a king (white by default) based on its position.
	 */
	private static final int[] KING_POSITIONS = { 2, 3, 5, -5, 0, -4, 6, 4, // a1
																			// ...
																			// h1
			-3, -3, -5, -5, -5, -5, -3, -3, // a2 ... h2
			-5, -5, -8, -8, -8, -8, -5, -5, // a3 ... h3
			-8, -8, -13, -13, -13, -13, -8, -8, // a4 ... h4
			-13, -13, -21, -21, -21, -21, -13, -13, // a5 ... h5
			-21, -21, -34, -34, -34, -34, -21, -21, // a6 ... h6
			-34, -34, -55, -55, -55, -55, -34, -34, // a7 ... h7
			-55, -55, -89, -89, -89, -89, -55, -55, // a8 ... h8
	};
	static {
		assert KING_POSITIONS.length == 64;
	}

	/**
	 * Bonus / Malus for a king (white by default) based on its position,
	 * endgame.
	 */
	private static final int[] KING_END_POSITIONS = { -5, -3, -1, 0, 0, -1, -3,
			-5, // a1 ... h1
			-3, 5, 5, 5, 5, 5, 5, -3, // a2 ... h2
			-1, 5, 10, 10, 10, 10, 5, -1, // a3 ... h3
			0, 5, 10, 15, 15, 10, 5, 0, // a4 ... h4
			0, 5, 10, 15, 15, 10, 5, 0, // a5 ... h5
			-1, 5, 10, 10, 10, 10, 5, -1, // a6 ... h6
			-3, 5, 5, 5, 5, 5, 5, -3, // a7 ... h7
			-5, -3, -1, 0, 0, -1, -3, -5, // a8 ... h8
	};
	static {
		assert KING_END_POSITIONS.length == 64;
	}

	/**
	 * Bonus/Malus d'un cavalier (blanc par défaut) en fonction de sa position.
	 */
	private static final int[] KNIGHT_POSITIONS = { -10, -5, -3, -1, -1, -3,
			-5, -10, // a1 ... h1
			-5, 0, 0, 3, 3, 0, 0, -5, // a2 ... h2
			-3, 0, 5, 5, 5, 5, 0, -3, // a3 ... h3
			-1, 1, 5, 10, 10, 5, 1, -1, // a4 ... h4
			-1, 1, 7, 12, 12, 7, 1, -1, // a5 ... h5
			-3, 0, 5, 7, 7, 5, 0, -3, // a6 ... h6
			-5, 0, 0, 3, 3, 0, 0, -5, // a7 ... h7
			-10, -5, -3, -1, -1, -3, -5, -10, // a8 ... h8
	};
	static {
		assert KNIGHT_POSITIONS.length == 64;
	}

	/**
	 * Bonus/Malus d'un pion (blanc par défaut) en fonction de sa position.
	 */
	private static final int[] PAWN_POSITIONS = { 0, 0, 0, 0, 0, 0, 0, 0, // a1
																			// ...
																			// h1
			0, 0, 0, -5, -5, 0, 0, 0, // a2 ... h2
			1, 2, 4, 4, 4, 3, 2, 1, // a3 ... h3
			2, 4, 7, 8, 8, 6, 4, 2, // a4 ... h4
			3, 6, 11, 12, 12, 9, 6, 3, // a5 ... h5
			4, 8, 12, 16, 16, 12, 8, 4, // a6 ... h6
			5, 10, 15, 20, 20, 15, 10, 5, // a7 ... h7
			100, 100, 100, 100, 100, 100, 100, 100, // a8 ... h9
	};
	static {
		assert PAWN_POSITIONS.length == 64;
	}

	/**
	 * Bonus/Malus d'une reine (blanche par défaut) en fonction de sa position.
	 */
	private static final int[] QUEEN_POSITIONS = { -5, -5, -5, 0, 0, -5, -5,
			-5, // a1 ... h1
			0, 0, 3, 3, 3, 0, 0, 0, // a2 ... h2
			0, 3, 3, 3, 3, 0, 0, 0, // a3 ... h3
			0, 0, 0, 5, 5, 0, 0, 0, // a4 ... h4
			0, 0, 0, 5, 5, 0, 0, 0, // a5 ... h5
			-5, -5, 0, 0, 0, 0, 0, 0, // a6 ... h6
			-5, -5, 0, 0, 0, 0, 0, 0, // a7 ... h7
			-5, -5, 0, 0, 0, 0, 0, 0, // a8 ... h8
	};
	static {
		assert QUEEN_POSITIONS.length == 64;
	}

	/**
	 * Bonus/Malus d'une tour (blanche par défaut) en fonction de sa position.
	 */
	private static final int[] ROOK_POSITIONS = { 0, 0, 0, 5, 5, 0, 0, 0, // a1
																			// ...
																			// h1
			-2, 0, 0, 0, 0, 0, 0, -2, // a2 ... h2
			-2, 0, 0, 0, 0, 0, 0, -2, // a3 ... h3
			-2, 0, 0, 0, 0, 0, 0, -2, // a4 ... h4
			-2, 0, 0, 0, 0, 0, 0, -2, // a5 ... h5
			-2, 0, 0, 0, 0, 0, 0, -2, // a6 ... h6
			10, 10, 10, 10, 10, 10, 10, 10, // a7 ... h7
			0, 0, 0, 0, 0, 0, 0, 0, // a8 ... h8
	};
	static {
		assert ROOK_POSITIONS.length == 64;
	}

	/**
	 * Creates a new instance.
	 */
	MobilityHeuristic() {
		// Rien de spécifique...
	}

	/**
	 * Reference the estimated value of a game state, for the functions of
	 * finding the best shot.
	 * 
	 * @param pEtat
	 *            State of the game.
	 * @param pTrait
	 *            Set to "true " if you want an evaluation from the perspective
	 *            of whites.
	 * @return Estimated value.
	 * @see Heuristic#evaluate(MoveGenerator,boolean)
	 */
	public int evaluate(final MoveGenerator pEtat, final boolean pTrait) {
		assert pEtat != null;

		final int[] pionsTrait = new int[FILE_COUNT];
		final int[] pionsAdversaire = new int[FILE_COUNT];

		int res = -pEtat.getHalfmoveCount() * 2;

		int nbPieces = 0;
		for (final Square s : Square.values()) {
			final Piece piece = pEtat.getPieceAt(s);
			if (piece != null) {
				nbPieces++;
				if (piece.getType() == PAWN) {
					final int idx = s.getFile();
					if (piece.isWhite() == pTrait) {
						pionsTrait[idx]++;
						if (pionsTrait[idx] > 1) {
							res -= 5;
						}
					} else {
						pionsAdversaire[idx]++;
						if (pionsAdversaire[idx] > 1) {
							res += 5;
						}
					}
				}
			}
		}

		for (final Square s : Square.values()) {
			final Piece piece = pEtat.getPieceAt(s);
			if (piece != null) {
				final boolean traitPiece = piece.isWhite();
				final PieceType typePiece = piece.getType();
				final int mat = typePiece.getValue();
				final int pos;
				final int mob;
				switch (typePiece) {
				case BISHOP:
					if (traitPiece) {
						pos = BISHOP_POSITIONS[s.getIndex()];
					} else {
						pos = BISHOP_POSITIONS[((RANK_COUNT - 1) - s.getRank())
								* FILE_COUNT + s.getFile()];
					}
					if (nbPieces >= MIDDLE_GAME) {
						mob = pEtat.getBishopTargets(s, traitPiece).length * 4;
					} else {
						mob = 0;
					}
					break;
				case KING:
					if ((pEtat.getFullmoveNumber() > 5)
							&& pEtat.isInCheck(traitPiece)) {
						pos = 0;
						if (pEtat.getValidMoves(traitPiece).length == 0) {
							// Mat : inutile d'aller plus loin...
							if (traitPiece == pTrait) {
								return MATE_VALUE;
							}

							return -MATE_VALUE;
						}

						// Malus pour un échec...
						mob = -250;
					} else {
						final int idx;
						if (traitPiece) {
							idx = s.getIndex();
						} else {
							idx = ((RANK_COUNT - 1) - s.getRank()) * FILE_COUNT
									+ s.getFile();
						}
						if (nbPieces >= END_GAME) {
							pos = KING_POSITIONS[idx];
						} else {
							pos = KING_END_POSITIONS[idx];
						}
						if ((pEtat.getFullmoveNumber() <= 32)
								&& pEtat.isCastled(traitPiece)) {
							// Pour favoriser le roque en début de partie...
							mob = 25;
						} else {
							mob = 0;
						}
					}
					break;
				case KNIGHT:
					if (traitPiece) {
						pos = KNIGHT_POSITIONS[s.getIndex()];
					} else {
						pos = KNIGHT_POSITIONS[((RANK_COUNT - 1) - s.getRank())
								* FILE_COUNT + s.getFile()];
					}
					if (nbPieces >= MIDDLE_GAME) {
						mob = pEtat.getKnightTargets(s, traitPiece).length * 4;
					} else {
						mob = 0;
					}
					break;
				case PAWN:
					if (traitPiece) {
						pos = PAWN_POSITIONS[s.getIndex()];
					} else {
						pos = PAWN_POSITIONS[((RANK_COUNT - 1) - s.getRank())
								* FILE_COUNT + s.getFile()];
					}
					if (nbPieces > END_GAME) {
						mob = 0;
					} else {
						mob = pos;
					}
					break;
				case QUEEN:
					int posReine = 0;
					if (traitPiece) {
						posReine = QUEEN_POSITIONS[s.getIndex()];
					} else {
						posReine = QUEEN_POSITIONS[((RANK_COUNT - 1) - s
								.getRank()) * FILE_COUNT + s.getFile()];
					}
					if (pEtat.getFullmoveNumber() < 12) {
						// Essayer d'éviter de déplacer la reine trop tôt...
						if ((traitPiece && (s.getIndex() != 3))
								|| ((!traitPiece) && (s.getIndex() != 59))) {
							posReine -= 30;
						}
					}
					pos = posReine;
					if ((nbPieces >= END_GAME) && (nbPieces <= MIDDLE_GAME)) {
						mob = pEtat.getQueenTargets(s, traitPiece).length;
					} else {
						mob = 0;
					}
					break;
				case ROOK:
					if (traitPiece) {
						pos = ROOK_POSITIONS[s.getIndex()];
					} else {
						pos = ROOK_POSITIONS[((RANK_COUNT - 1) - s.getRank())
								* FILE_COUNT + s.getFile()];
					}
					if (nbPieces >= END_GAME) {
						final int nbPions;
						if (traitPiece == pTrait) {
							nbPions = pionsTrait[s.getFile()];
						} else {
							nbPions = pionsAdversaire[s.getFile()];
						}
						if (nbPions == 0) {
							mob = 10;
						} else {
							mob = 0;
						}
					} else {
						mob = pEtat.getRookTargets(s, traitPiece).length * 2;
					}
					break;
				default:
					assert false;
					pos = 0;
					mob = 0;
				}
				int att = 0;
				if (typePiece != KING) {
					if (pEtat.isAttacked(s, traitPiece)) {
						att += mat / 20;
					}
					if (pEtat.isAttacked(s, !traitPiece)) {
						att -= mat / 10;
					}
				}
				final int score = mat + pos + mob + att;
				if (traitPiece == pTrait) {
					res += score;
				} else {
					res -= score;
				}
			}
		}

		return res;
	}
}
